【D3.js】力导向布局 + 圆形图片展示的人物关系

前言

使用d3的力学图(力导向图)与生活中常见的人物关系图结合,已经有了很好的例子: 【 D3.js 进阶系列 — 2.0 】 力学图 + 人物关系图,博主实现了下面这种样式,已经相当不错了。

但是对于想把节点的方形图片换成圆形头像,网上的资料却很少,本例子就在上面的实例的基础上实现圆形头像,让人物关系图看起来更漂亮了一个档次。两个主要文件如下:

# index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
<html>
<head>
<meta charset="utf-8">
<title>Force</title>
<style>

.nodetext {
font-size: 12px ;
font-family: SimSun;
fill:#000000;
}

.linetext {
font-size: 12px ;
font-family: SimSun;
fill:#1f77b4;
fill-opacity:0.0;
}

.circleImg {
stroke: #ff7f0e;
stroke-width: 1.5px;
}

</style></head>

<body>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>

var width = 900;
var height = 800;
var img_w = 77;
var img_h = 80;
var radius = 30; //圆形半径

var svg = d3.select("body").append("svg")
.attr("width",width)
.attr("height",height);


d3.json("relation.json",function(error,root){

if( error ){
return console.log(error);
}
console.log(root);

//D3力导向布局
var force = d3.layout.force()
.nodes(root.nodes)
.links(root.edges)
.size([width,height])
.linkDistance(200)
.charge(-1500)
.start();

//边
var edges_line = svg.selectAll("line")
.data(root.edges)
.enter()
.append("line")
.style("stroke","#ccc")
.style("stroke-width",1);

//边上的文字(人物之间的关系)
var edges_text = svg.selectAll(".linetext")
.data(root.edges)
.enter()
.append("text")
.attr("class","linetext")
.text(function(d){
return d.relation;
});


// 圆形图片节点(人物头像)
var nodes_img = svg.selectAll("image")
.data(root.nodes)
.enter()
.append("circle")
.attr("class", "circleImg")
.attr("r", radius)
.attr("fill", function(d, i){

//创建圆形图片
var defs = svg.append("defs").attr("id", "imgdefs")

var catpattern = defs.append("pattern")
.attr("id", "catpattern" + i)
.attr("height", 1)
.attr("width", 1)

catpattern.append("image")
.attr("x", - (img_w / 2 - radius))
.attr("y", - (img_h / 2 - radius))
.attr("width", img_w)
.attr("height", img_h)
.attr("xlink:href", d.image)

return "url(#catpattern" + i + ")";

})
.on("mouseover",function(d,i){
//显示连接线上的文字
edges_text.style("fill-opacity",function(edge){
if( edge.source === d || edge.target === d ){
return 1.0;
}
});
})
.on("mouseout",function(d,i){
//隐去连接线上的文字
edges_text.style("fill-opacity",function(edge){
if( edge.source === d || edge.target === d ){
return 0.0;
}
});
})
.call(force.drag);


var text_dx = -20;
var text_dy = 20;

var nodes_text = svg.selectAll(".nodetext")
.data(root.nodes)
.enter()
.append("text")
.attr("class","nodetext")
.attr("dx",text_dx)
.attr("dy",text_dy)
.text(function(d){
return d.name;
});


force.on("tick", function(){

//限制结点的边界
root.nodes.forEach(function(d,i){
d.x = d.x - img_w/2 < 0 ? img_w/2 : d.x ;
d.x = d.x + img_w/2 > width ? width - img_w/2 : d.x ;
d.y = d.y - img_h/2 < 0 ? img_h/2 : d.y ;
d.y = d.y + img_h/2 + text_dy > height ? height - img_h/2 - text_dy : d.y ;
});

//更新连接线的位置
edges_line.attr("x1",function(d){ return d.source.x; });
edges_line.attr("y1",function(d){ return d.source.y; });
edges_line.attr("x2",function(d){ return d.target.x; });
edges_line.attr("y2",function(d){ return d.target.y; });

//更新连接线上文字的位置
edges_text.attr("x",function(d){ return (d.source.x + d.target.x) / 2 ; });
edges_text.attr("y",function(d){ return (d.source.y + d.target.y) / 2 ; });


//更新结点图片和文字
nodes_img.attr("cx",function(d){ return d.x });
nodes_img.attr("cy",function(d){ return d.y });

nodes_text.attr("x",function(d){ return d.x });
nodes_text.attr("y",function(d){ return d.y + img_w/2; });
});
});

</script>

</body>
</html>

# relation.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
{
"nodes":[
{ "name": "云天河" , "image" : "tianhe.png" },
{ "name": "韩菱纱" , "image" : "lingsha.png" },
{ "name": "柳梦璃" , "image" : "mengli.png" },
{ "name": "慕容紫英" , "image" : "ziying.png" },
{ "name": "云天青" , "image" : "tianqing.png" },
{ "name": "夙玉" , "image" : "suyu.png" },
{ "name": "玄霄" , "image" : "xuanxiao.png" },
{ "name": "夙瑶" , "image" : "suyao.png" },
{ "name": "太清" , "image" : "taiqing.png" },
{ "name": "宗炼" , "image" : "zonglian.png" },
{ "name": "婵幽" , "image" : "chanyou.png" },
{ "name": "奚仲" , "image" : "xizhong.png" },
{ "name": "归邪" , "image" : "guixie.png" }
],
"edges":[
{ "source": 0 , "target": 1 , "relation":"挚友" },
{ "source": 0 , "target": 2 , "relation":"挚友" },
{ "source": 0 , "target": 3 , "relation":"挚友" },
{ "source": 1 , "target": 2 , "relation":"挚友" },
{ "source": 1 , "target": 3 , "relation":"挚友" },
{ "source": 2 , "target": 3 , "relation":"挚友" },
{ "source": 0 , "target": 4 , "relation":"父子" },
{ "source": 0 , "target": 5 , "relation":"母子" },
{ "source": 4 , "target": 5 , "relation":"夫妻" },
{ "source": 0 , "target": 6 , "relation":"义兄弟" },
{ "source": 4 , "target": 6 , "relation":"同门" },
{ "source": 5 , "target": 6 , "relation":"同门" },
{ "source": 4 , "target": 7 , "relation":"同门" },
{ "source": 5 , "target": 7 , "relation":"同门" },
{ "source": 6 , "target": 7 , "relation":"同门" },
{ "source": 4 , "target": 8 , "relation":"师徒" },
{ "source": 5 , "target": 8 , "relation":"师徒" },
{ "source": 6 , "target": 8 , "relation":"师徒" },
{ "source": 7 , "target": 8 , "relation":"师徒" },
{ "source": 8 , "target": 9 , "relation":"同门" },
{ "source": 3 , "target": 9 , "relation":"师徒" },
{ "source": 2 , "target": 10 , "relation":"母子" },
{ "source": 10 , "target": 11 , "relation":"主臣" },
{ "source": 10 , "target": 12 , "relation":"主臣" },
{ "source": 11 , "target": 12 , "relation":"同僚" }
]
}

【参考资料】

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2019 iTimeTraveler All Rights Reserved.

访客数 : | 访问量 :